#include <iostream>
#include <Utils.h>
#include <unordered_map>
using namespace std;
using namespace lyxutils::abbreviations;

void printUsage(){
    cout<<"usage:counter_code folder [folder2...]"<<endl;
    cout<<"      count total lines of code in a given folder"<<endl;
    cout<<"arguments:"<<endl;
    cout<<"  -h   print this help message"<<endl;
    cout<<"  -l   searching hide folders(eg:folders start with `.`) as well"<<endl;
    cout<<"  -a   list all files that are searched"<<endl;
    cout<<"  -d   disable .gitignore file(don't apply git ignore rules automatically)"<<endl;
    cout<<"       note:ignore feature is auto applied(the program only supports basic"<<endl;
    cout<<"       ignore rule,meaning reject folders and files listed in .gitignore)"<<endl;
    cout<<"  -e=folder1,file2,folder|file..."<<endl;
    cout<<"       exclude files or folders(this rule apply to every folder searched)"<<endl;
    cout<<"supported languages:"<<endl;
    cout<<"  -c   count c/c++ code lines(.h,.hpp,.c and .cpp files)"<<endl;
    cout<<"       if no other file suffix set,this option is automatically enabled"<<endl;
    cout<<"  -p   count python code lines(.py files)"<<endl;
    cout<<"  -j   count java code lines(.java files)"<<endl;
    cout<<"  -#   count c# code lines(.cs files)"<<endl;
    cout<<"  -g   count go code lines(.go files)"<<endl;
    cout<<"  -m   count matlab code lines(.m files)"<<endl;
    cout<<"  -s   count shell script code lines(.sh files)"<<endl;
    cout<<"  -t   count plain text lines(.txt files)"<<endl;
    cout<<"  -w   web languages such as html,css and javascript(.html,.htm,xhtml,.css and .js files)"<<endl;
    cout<<"  -v   web language narrowed down(thus w->v)=>javascript only(.js files)"<<endl;
}

void recursive_ls(string folder,string suffix,vector<string> &files, vector<string> ignore_patterns, bool apply_ignore, bool ls_hide=false){
    //folder is assumed to exist
    vector<string> sub_files=xos::ls(xos::abspath(folder));
    //git ignore rules are accumulative
    if(apply_ignore){
        ifstream ifs(xos::abspath(folder+"/.gitignore"));
        string line;
        while(getline(ifs,line)){
            if(line!="")ignore_patterns.push_back(line);
        }
        ifs.close();
    }
    for(string item:sub_files){
        //check if item exists in ignore patterns
        if(std::find(ignore_patterns.begin(),ignore_patterns.end(),
                xos::fullname(item)) != ignore_patterns.end())continue;
        if(xos::file(item) && xos::suffix(item) == suffix){
            files.push_back(item);
        }
        if(xos::directory(item) && (ls_hide || xos::fullname(item).substr(0,1)!=".")){
            recursive_ls(item,suffix,files,ignore_patterns,apply_ignore,ls_hide);
        }
    }
}

int count_lines(string filename){
    ifstream ifs(xos::abspath(filename));
    int count=0;
    string line;
    while(getline(ifs,line))count++;
    ifs.close();
    return count;
}

//return 0 if the two folders are mutual exclusive
//return 1 if folder1 contains folder2
int duplicate(string folder1,string folder2){
    folder1=xos::abspath(folder1);
    folder2=xos::abspath(folder2);
    if(str::count(folder2,folder1,0,folder2.size()))return 1;
    return 0;
}

int main(int argc, char **argv) {
    xio::CLParser parser;
    parser.parse(argc,argv);
    vector<string> folders=parser.getParameters();
    if(parser.hasOption("h")||folders.size()==0){
        printUsage();
        return 1;
    }
    unordered_map<string,vector<string> > suffix2files;
    unordered_map<string,int> suffix2code;
    //eliminate duplicate folder
    if(folders.size()>1){
        vector<int> removal(folders.size(),0);
        for(int i=0;i<folders.size();++i){
            if(removal[i])continue;
            for(int j=i+1;j<folders.size();++j){
                if(removal[j])continue;
                if(duplicate(folders[i],folders[j])){
                    removal[j]=1;
                }
                else if(duplicate(folders[j],folders[i])){
                    removal[i]=1;
                    break;
                }
            }
        }
        auto it=folders.begin();
        auto rem_it=removal.begin();
        for(;it!=folders.end();){
            if(*rem_it){
                it=folders.erase(it);
                rem_it=removal.erase(rem_it);
            }
            else{
                ++it;
                ++rem_it;
            }
        }
    }
    //options
    auto add_suffix=[&suffix2files,&suffix2code](string suffix){
        suffix2files[suffix]=vector<string>();
        suffix2code[suffix]=0;
    };
    if(parser.hasOption("p"))add_suffix("py");//python files
    if(parser.hasOption("j"))add_suffix("java");//java files
    if(parser.hasOption("#"))add_suffix("cs");//c# files
    if(parser.hasOption("g"))add_suffix("go");//golang files
    if(parser.hasOption("m"))add_suffix("m");//matlab files
    if(parser.hasOption("s"))add_suffix("sh");//shell script files
    if(parser.hasOption("t"))add_suffix("txt");//plain text files
    if(parser.hasOption("w")){
        add_suffix("html");
        add_suffix("htm");
        add_suffix("xhtml");
        add_suffix("css");
        add_suffix("js");
    }
    else if(parser.hasOption("v"))add_suffix("js");//javascript files
    if(parser.hasOption("c")||suffix2files.size()==0){//c/c++ files
        add_suffix("h");
        add_suffix("hpp");
        add_suffix("c");
        add_suffix("cpp");
    }
    bool ls_hide=parser.hasOption("l");
    bool apply_ignore=!parser.hasOption("d");
    vector<string> ignore_patterns;
    if(parser.hasOption("e"))ignore_patterns=str::split(parser.getOptionArg("e"),",");
    //count files and lines of code
    for(int i=0;i<folders.size();++i){
        if(!xos::directory(folders[i])){
            cout<<"folder:"<<folders[i]<<" not exist.skipped"<<endl;
            continue;
        }
        for(auto &sf:suffix2files){
            recursive_ls(folders[i],sf.first,sf.second,ignore_patterns,apply_ignore,ls_hide);
        }
    }
    for(auto sf:suffix2files){
        for(auto dir:sf.second)suffix2code[sf.first]+=count_lines(dir);
    }
    //report
    int cols=3;
    int col_width=15;
    if(parser.hasOption("a")){
        cout<<str::center("files examined",col_width*cols,'=')<<endl;
        for(auto sf:suffix2files){
            cout<<sf.first<<"("<<sf.second.size()<<"):"<<endl;
            cout<<"-----------"<<endl;
            int line=col_width*cols;
            for(int i=0;i<sf.second.size();++i){
                string name=xos::relpath(sf.second[i]);
                int pos=0;
                while(pos<name.size()){
                    int sz=name.size()-pos<line?name.size()-pos:line;
                    cout<<name.substr(pos,sz)<<endl;
                    pos+=line;
                }
            }
        }
    }
    cout<<str::center("report",col_width*cols,'=')<<endl;
    cout<<str::center("suffix",col_width,' ')
        <<str::center("files",col_width,' ')
        <<str::center("code",col_width,' ')<<endl;
    for(auto sf:suffix2files){
        cout<<str::center(sf.first,col_width,' ')
            <<str::center(str::type2str(sf.second.size()),col_width,' ')
            <<str::center(str::type2str(suffix2code[sf.first]),col_width,' ')<<endl;
    }
    int f_total=0;
    int line_total=0;
    for(auto sf:suffix2files){
        f_total+=sf.second.size();
        line_total+=suffix2code[sf.first];
    }
    cout<<endl<<str::left("total=>",col_width,' ')
        <<str::left("files:"+str::type2str(f_total),col_width,' ')
        <<str::left("code:"+str::type2str(line_total),col_width,' ')<<endl;
}